package com.orange.server;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.webapp.WebAppClassLoader;
import org.eclipse.jetty.webapp.WebAppContext;

public class ServerRunner {

    private static final String DEFAULT_WEBAPP_PATH = "src/main/webapp";

    private static final String WINDOWS_WEBDEFAULT_PATH = "jr-webdefault-windows.xml";

    private static final int DEFAULT_PORT = 8080;

    private static final String DEFAULT_CONTEXT_PATH = "/";

    public static final String[] DEFAULT_TLD_JAR_NAMES = new String[] { "sitemesh", "spring-webmvc", "shiro-web", "springside-core" };

    public static final String[] DEFAULT_RELOAD_CLASSPATHS = new String[] { "target/classes", "target/test-classes" };

    public ServerRunner() {
        Collections.addAll(tldJarNames, DEFAULT_TLD_JAR_NAMES);
        setPort(DEFAULT_PORT);
        setContextPath(DEFAULT_CONTEXT_PATH);
        setWebPath(DEFAULT_WEBAPP_PATH);
        setWebDefault(WINDOWS_WEBDEFAULT_PATH);
        Collections.addAll(reloadClassPaths, DEFAULT_RELOAD_CLASSPATHS);
    }


    /**
     * 启动jetty
     */
    public void start() {
        try {
            //启动Jetty
            createServerInSource();

            setTldJarNames();

            server.start();

            System.out.println("服务器启动成功： http://localhost:" + port + contextPath);
            System.out.println("敲回车重载应用");

            //等待用户输入回车重载应用.
            while (true) {
                char c = (char) System.in.read();
                if (c == '\n') {
                    reloadContext();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    private Server server;

    /**
     * 创建用于开发运行调试的Jetty Server, 以src/main/webapp为Web应用目录.
     */
    private void createServerInSource() throws IOException {
        server = new Server();
        //设置在JVM退出时关闭Jetty的钩子。
        server.setStopAtShutdown(true);

        SelectChannelConnector connector = new SelectChannelConnector();
        connector.setPort(port);
        //解决Windows下重复启动Jetty居然不报告端口冲突的问题.
        connector.setReuseAddress(false);
        server.setConnectors(new Connector[] { connector });


        List<Handler> handlers = new ArrayList<Handler>();

        WebAppContext mainContext = new WebAppContext(webPath, contextPath);

        WebAppClassLoader classLoader = new WebAppClassLoader(mainContext);
        for (String path : reloadClassPaths) {
            classLoader.addClassPath(path);
        }
        mainContext.setClassLoader(classLoader);

        //修改webdefault.xml，解决Windows下Jetty Lock住静态文件的问题.
        mainContext.setDefaultsDescriptor(webDefault);

        handlers.add(mainContext);

        for (String context : otherContext.keySet()) {
            WebAppContext wa = new WebAppContext(otherContext.get(context), context);
            wa.setDefaultsDescriptor(webDefault);
            handlers.add(wa);
        }

        ContextHandlerCollection contexts = new ContextHandlerCollection();
        contexts.setHandlers(handlers.toArray(new Handler[0]));

        server.setHandler(contexts);
    }


    /**
     * 设置除jstl-*.jar外其他含tld文件的jar包的名称.
     * jar名称不需要版本号，如sitemesh, shiro-web
     */
    private void setTldJarNames() {
        List<String> jarNameExprssions = new ArrayList<String>();
        jarNameExprssions.add(".*/jstl-[^/]*\\.jar$");
        jarNameExprssions.add(".*/.*taglibs[^/]*\\.jar$");
        for (String jarName : tldJarNames) {
            jarNameExprssions.add(".*/" + jarName + "-[^/]*\\.jar$");
        }
        getMainContext().setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", join(jarNameExprssions, "|"));
    }

    private WebAppContext getMainContext() {
        ContextHandlerCollection contexts = (ContextHandlerCollection) server.getHandler();
        return (WebAppContext) contexts.getHandlers()[0];
    }

    /**
     * 快速重新启动application，重载target/classes与target/test-classes.
     */
    public void reloadContext() throws Exception {
        WebAppContext context = getMainContext();

        System.out.println("应用重新载入中...");
        context.stop();

        WebAppClassLoader classLoader = new WebAppClassLoader(context);
        for (String path : reloadClassPaths) {
            classLoader.addClassPath(path);
        }
        context.setClassLoader(classLoader);

        context.start();

        System.out.println("应用重载成功");
    }


    private List<String> reloadClassPaths = new ArrayList<String>();

    /**
     * 添加重载的classpath路径，
     * @param path 路径 默认为 target/classes 和 target/test-classes
     */
    public void addReloadClassPaths(String path) {
        reloadClassPaths.add(path);
    }

    private String webDefault;

    /**
     * 设置相对项目的默认的web.xml路径。这个web.xml为载入项目web.xml之前载入的默认配置
     * @param webDefault web.xml文件路径
     */
    public void setWebDefault(String webDefault) {
        this.webDefault = webDefault;
    }

    private String webPath;

    /**
     * 设置相对项目的webroot目录
     * @param webPath root目录。默认为 src/main/webapp。
     */
    public void setWebPath(String webPath) {
        this.webPath = webPath;
    }

    private int port;

    /**
     * 设置启动端口号
     * @param port 端口号，默认为8080
     */
    public void setPort(int port) {
        this.port = port;
    }

    private String contextPath;

    /**
     * 设置启动发布的路径
     * @param contextPath 发布路径，默认为/
     */
    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }

    private final List<String> tldJarNames = new ArrayList<String>();

    /**
     * 添加包含tld标签的jar包名，容器启动时需载入
     * @param jarName jar包名，不包含版本
     */
    public void addTldJarName(String jarName) {
        tldJarNames.add(jarName);
    }

    private final Map<String, String> otherContext = new HashMap<String, String>();

    /**
     * 添加其他的发布路径
     * @param contextPath 发布路径 例如：/data/tmp
     * @param diskPath 映射磁盘的路径 例如： d:\data\tmp
     */
    public void addContext(String contextPath, String diskPath) {
        otherContext.put(contextPath, diskPath);
    }




    private static String join(List<String> list, String str) {
        StringBuilder sb = new StringBuilder();
        for (String s : list) {
            sb.append(s).append(str);
        }
        if (sb.length() > 0) {
            sb.setLength(sb.length() - 1);
        }
        return sb.toString();
    }

}
